
#include <Inklude/Internal/ikCTcpBoost.h>
#include <Inklude/ikCThread.h>
#include <boost/bind.hpp>
#include <memory>

using namespace Inklude::Internal;
using boost::asio::ip::tcp;

#define IF_NOT_SAME_CHANGE(A, B) \
        if (A.get() != B.get())  \
        {                        \
           A = B;                \
        }                       

namespace Inklude
{
namespace Internal
{
template <typename T>
T _async_routine(std::shared_ptr<std::function<void()> > func, bool& async, bool& asyncTried, T& RetValInSuccess)
{
    T retVal = 0;
    if (!asyncTried)
    {
        async = false;
        asyncTried = true;
        ikCThread::RunThread(func);
    }

    if (async)
    {
        retVal = RetValInSuccess;
    }
    return retVal;
}
}
}


ikCTcpBoost::ikCTcpBoost()
{
}

ikCTcpBoost::ikCTcpBoost(std::shared_ptr<boost::asio::io_service> pIOService, std::shared_ptr<tcp::acceptor> pAcceptor)
{
    m_pIOService = pIOService;
    m_pAcceptor = pAcceptor;    
}

ikCTcpBoost::ikCTcpBoost(std::shared_ptr<boost::asio::io_service> pIOService)
{
    m_pIOService = pIOService;
}

ikCTcpBoost::ikCTcpBoost(std::shared_ptr<tcp::acceptor> pAcceptor)
{
    m_pAcceptor = pAcceptor;
}

ikCTcpBoost::~ikCTcpBoost()
{
}

void ikCTcpBoost::_init()
{
    if (m_pIOService.use_count() == 0)
    {
        m_pIOService.reset(new boost::asio::io_service());
    }

    m_pRemoteEndPoint.reset(new tcp::endpoint);
    m_pSocket.reset(new tcp::socket(*m_pIOService));
}

void ikCTcpBoost::Initialize(unsigned int nRecvPort)
{
    _init();
    
    m_pAcceptor.reset(new tcp::acceptor(*m_pIOService, tcp::endpoint(tcp::v4(), nRecvPort)));
}

void ikCTcpBoost::Run()
{
    if (isRun)
    {
        isRun = false;
        std::shared_ptr<std::function<void()> > ioThread(new std::function<void()>( [=]()
        {
            m_pIOService->run();
        }
        ));
        Inklude::ikCThread::RunThread(ioThread);        
    }
}

void ikCTcpBoost::SetDestinationAddress(const char* IpAddress, unsigned int nSentPort)
{
    _init();
    Inklude::ikCString sendPort;
    sendPort = (int)nSentPort;
    
    tcp::resolver _resolver(*(m_pIOService.get()));
    tcp::resolver::query _query(tcp::v4(),  IpAddress, sendPort.c_str());
    tcp::resolver::iterator iter = _resolver.resolve(_query);

    m_pRemoteEndPoint.reset(new tcp::endpoint((iter->endpoint()).address(), (iter->endpoint()).port()));
}

bool ikCTcpBoost::Accept()
{
    bool retVal = true;
    try
    {
        m_pAcceptor->accept(*m_pSocket);
        //m_pAcceptor->accept(*m_pSocketStream->rdbuf());
    }
    catch (...)
    {
        retVal = false;
    }
    return retVal;
}

// Each Async Routine should be wrapped up using template / std::function
// There are many dulplicated codes.
bool ikCTcpBoost::AsyncAccept(AsyncHandler endHandler)
{
    IF_NOT_SAME_CHANGE(m_acceptHandler, endHandler);
    m_pAcceptor->async_accept(*m_pSocket, boost::bind(&ikCTcpBoost::handle_accept, this, boost::asio::placeholders::error));
    return false;
}

bool ikCTcpBoost::ConnectToServer()
{
    bool retVal = true;
    try
    {
        m_pSocket->connect((*m_pRemoteEndPoint));
        //m_pSocketStream->connect((*m_pRemoteEndPoint));
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
        retVal = false;
    }
    return retVal;
}

bool ikCTcpBoost::AsyncConnectToServer(AsyncHandler endHandler)
{
    IF_NOT_SAME_CHANGE(m_connHandler, endHandler);
    m_pSocket->async_connect(*m_pRemoteEndPoint, boost::bind(&ikCTcpBoost::handle_connect, this, boost::asio::placeholders::error));
    return true;
}

size_t ikCTcpBoost::SyncWrite(void* pBuffer, size_t nDataSize)
{
    __int64 retVal = 0;
    try
    {
        boost::asio::write(*m_pSocket, boost::asio::buffer((char*)pBuffer, nDataSize));
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    return nDataSize;
}

size_t ikCTcpBoost::AsyncWrite(void* pBuffer, size_t nDataSize, AsyncHandler endHandler)
{
    IF_NOT_SAME_CHANGE(m_sendHandler, endHandler);
    boost::asio::async_write( (*m_pSocket), 
                              boost::asio::buffer(pBuffer, nDataSize), 
                              boost::bind(&ikCTcpBoost::handle_send, 
                                          this,
                                          boost::asio::placeholders::error, 
                                          boost::asio::placeholders::bytes_transferred));
    return 0;
}

size_t ikCTcpBoost::SyncRead(void* pBuffer, size_t nDataSize)
{
    size_t retVal = 0;
    try
    {
        size_t recvSize = 0;
        boost::asio::read(*m_pSocket, boost::asio::buffer((char*)pBuffer, nDataSize));
    }
    catch (std::exception& e)
    {
        std::cerr << e.what() << std::endl;
    }
    return nDataSize;
}


size_t ikCTcpBoost::AsyncRead(void* pBuffer, size_t nDataSize, AsyncHandler endHandler)
{
    IF_NOT_SAME_CHANGE(m_receiveHandler, endHandler);
    boost::asio::async_read( (*m_pSocket), 
                             boost::asio::buffer(pBuffer, nDataSize), 
                             boost::bind(&ikCTcpBoost::handle_receive, 
                                         this,
                                         boost::asio::placeholders::error, 
                                         boost::asio::placeholders::bytes_transferred));
    return 0;
}

void ikCTcpBoost::Close()
{
    m_pSocket->close();
}

void ikCTcpBoost::handle_receive(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    (*m_receiveHandler)(bytes_transferred);
}

void ikCTcpBoost::handle_send(const boost::system::error_code& error, std::size_t bytes_transferred)
{
    (*m_sendHandler)(bytes_transferred);
}

void ikCTcpBoost::handle_connect(const boost::system::error_code& error)
{
    (*m_connHandler)(0);
}

void ikCTcpBoost::handle_accept(const boost::system::error_code& error)
{
    (*m_acceptHandler)(0);
}
